package libs;


import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 *              This class represents an attendance book.
 *
 * @author      Jan Papousek
 * @version     2009-03-02
 */
public class AttendanceImpl implements Attendance {

    /**
     *         Subjects and their seminars.
     */
    private Map<Subject, Set<Seminar>> seminars;

    /**
     *         Students and their seminars.
     */
    private Map<Student, Set<Seminar>> students;

    /**
     *          Presence of the students.
     */
    private Map<Seminar, Map<Student, Set<Date>>> presence;

    /**
     *          Absence of the students.
     */
    private Map<Seminar, Map<Student, Set<Date>>> absence;

    public AttendanceImpl() {
        this.seminars = new HashMap<Subject, Set<Seminar>>();
        this.students = new HashMap<Student, Set<Seminar>>();
        this.absence = new HashMap<Seminar, Map<Student, Set<Date>>>();
        this.presence = new HashMap<Seminar, Map<Student, Set<Date>>>();
    }

    public void absent(Student student, Seminar seminar) throws AttendanceException {
        this.absent(student, seminar,new Date());
    }

    public void absent(Student student, Seminar seminar, Date date) throws AttendanceException {
        if (student == null) {
            throw new NullPointerException("The argument 'student' is null.");
        }
        if (seminar == null) {
            throw new NullPointerException("The argument 'seminar' is null.");
        }
        if ((this.students.get(student) == null) || !((this.students.get(student).contains(seminar)))) {
            throw new AttendanceException("The student '"
                    + student.getName() +
                    "' is not registered in seminar "
                    + seminar.getSubject().getName() + "[" + seminar.getNumber() + "]"
            );
        }
        this.absence.get(seminar).get(student).add(this.processDate(date));
    }

    public boolean addSeminar (Seminar seminar) {
        if (seminar == null) {
            throw new NullPointerException("The argument 'seminar' is null.");
        }
        if (!this.seminars.containsKey(seminar.getSubject())) {
            this.seminars.put(seminar.getSubject(), new HashSet<Seminar>());
            this.absence.put(seminar, new HashMap<Student, Set<Date>>());
            this.presence.put(seminar, new HashMap<Student, Set<Date>>());
        }
        return this.seminars.get(seminar.getSubject()).add(seminar);
    }

    public boolean addStudent (Student student, Seminar seminar) throws AttendanceException {
        if (student == null) {
            throw new NullPointerException("The argument 'student' is null.");
        }
        if (seminar == null) {
            throw new NullPointerException("The argument 'seminar' is null.");
        }
        if (!this.absence.containsKey(seminar)) {
            throw new AttendanceException("The seminar '"
                    + seminar.getSubject().getName() + "[" + seminar.getNumber() + "]" +
                    "' does not exist."
            );
        }
        if (!this.students.containsKey(student)) {
            this.students.put(student,new HashSet<Seminar>());
            this.presence.get(seminar).put(student, new HashSet<Date>());
            this.absence.get(seminar).put(student, new HashSet<Date>());
        }
        return this.students.get(student).add(seminar);
    }

    public void changeAttendace (Student student, Seminar seminar, Date date) throws AttendanceException {
        if (student == null) {
            throw new NullPointerException("The argument 'student' is null.");
        }
        if (seminar == null) {
            throw new NullPointerException("The argument 'seminar' is null.");
        }
        if ((this.students.get(student) == null) || !((this.students.get(student).contains(seminar)))) {
            throw new AttendanceException("The student '"
                    + student.getName() +
                    "' is not registered in seminar "
                    + seminar.getSubject().getName() + "[" + seminar.getNumber() + "]"
            );
        }
        Date processedDate = this.processDate(date);
        if (this.absence.get(seminar).get(student).remove(processedDate)) {
            this.absence.get(seminar).get(student).add(processedDate);
        }
        else {
            if (this.presence.get(seminar).get(student).remove(processedDate)) {
                this.presence.get(seminar).get(student).add(processedDate);
            }
            else {
                throw new AttendanceException("The student '" + student.getId() + "' has no absence and no presence on this day: " + processedDate);
            }
        }
    }

    public Map<Student,Set<Date>> getAbsence (Seminar seminar) {
        if (seminar == null) {
            throw new NullPointerException("The argument 'seminar' is null.");
        }
        return Collections.unmodifiableMap(this.absence.get(seminar));
    }

    public Map<Student,Set<Date>> getPresence (Seminar seminar) {
        if (seminar == null) {
            throw new NullPointerException("The argument 'seminar' is null.");
        }
        return Collections.unmodifiableMap(this.presence.get(seminar));
    }

    public Set<Seminar> getSeminars () {
        Set<Seminar> result = new HashSet<Seminar>();
        for (Set<Seminar> seminarSet : this.seminars.values()) {
            result.addAll(seminarSet);
        }
        return Collections.unmodifiableSet(result);
    }

    public Set<Seminar> getSeminars (Subject subject) {
        if (subject == null) {
            throw new NullPointerException("The argument 'subject' is null.");
        }
        if (this.seminars.get(subject) != null) {
            return Collections.unmodifiableSet(this.seminars.get(subject));
        }
        else {
            return Collections.unmodifiableSet(new HashSet<Seminar>());
        }
    }

    public Set<Student> getStudents() {
        return Collections.unmodifiableSet(this.students.keySet());
    }

    public Set<Student> getStudents (Seminar seminar) {
        if (seminar == null) {
            throw new NullPointerException("The argument 'seminar' is null.");
        }
        if (this.absence.get(seminar) != null) {
            return Collections.unmodifiableSet(this.absence.get(seminar).keySet());
        }
        else {
            return Collections.unmodifiableSet(new HashSet<Student>());
        }
    }

    public void load () {
        throw new UnsupportedOperationException("The method 'load()' is not implemented.");
    }

    public void present (Student student, Seminar seminar) throws AttendanceException {
        this.present(student, seminar, new Date());
    }

    public void present (Student student, Seminar seminar, Date date) throws AttendanceException {
        if (student == null) {
            throw new NullPointerException("The argument 'student' is null.");
        }
        if (seminar == null) {
            throw new NullPointerException("The argument 'seminar' is null.");
        }
        if (date == null) {
            throw new NullPointerException("The argument 'date' is null.");
        }
        if ((this.students.get(student) == null) || !((this.students.get(student).contains(seminar)))) {
            throw new AttendanceException("The student '"
                    + student.getName() +
                    "' is not registered in seminar "
                    + seminar.getSubject().getName() + "[" + seminar.getNumber() + "]"
            );
        }
        this.presence.get(seminar).get(student).add(this.processDate(date));
    }

    protected Date processDate(Date date) {
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        Calendar result = new GregorianCalendar();
        result.clear();
        result.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH));
        return result.getTime();
    }

    public boolean removeSeminar (Seminar seminar) {
        if (seminar == null) {
            throw new NullPointerException("The argument 'seminar' is null.");
        }
        if ((this.seminars.get(seminar.getSubject()) == null) ||(!this.seminars.get(seminar.getSubject()).contains(seminar))) {
            return false;
        }
        for (Student stud : this.absence.get(seminar).keySet()) {
            this.students.get(stud).remove(seminar);
            if (this.students.get(stud).size() == 0) {
                this.students.remove(stud);
            }
        }
        this.absence.remove(seminar);
        this.presence.remove(seminar);
        return true;
    }

    public boolean removeStudent (Student student, Seminar seminar) {
        if (student == null) {
            throw new NullPointerException("The argument 'student' is null.");
        }
        if (seminar == null) {
            throw new NullPointerException("The argument 'seminar' is null.");
        }
        this.absence.get(seminar).remove(student);
        this.presence.get(seminar).remove(student);
        if (this.students.get(student) == null) {
            return false;
        }
        else if (this.students.get(student).size() == 1) {
            this.students.remove(student);
            return true;
        }
        else {
            return this.students.get(student).remove(seminar);
        }
    }

    public boolean removeStudent (Student student) {
        if (student == null) {
            throw new NullPointerException("The argument 'student' is null.");
        }
        for (Seminar sem : this.students.get(student)) {
            this.absence.get(sem).remove(student);
            this.presence.get(sem).remove(student);
        }
        if (this.students.get(student) != null) {
            this.students.remove(student);
            return true;
        }
        else {
            return false;
        }
    }

    public void save () {
        throw new UnsupportedOperationException("The method 'save()' is not implemented.");
    }

    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date);
        Calendar calendar = new GregorianCalendar();
        calendar.setTime(date);
        Calendar result = new GregorianCalendar();
        result.clear();
        result.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH));
        System.out.println(result.getTime());
    }
}